package org.icop.virtdoc;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class VirtualDocVSM {
			    
    private List<String> terms = new ArrayList<String>();
    private Map<String,Integer> termToDocOccurrences = new HashMap<String, Integer>();
    private double numberOfDocs = 0;
    
    /*
     * Caching variables.
     */
    private Map<VirtualDoc, float[]> vectorCache = new HashMap<VirtualDoc, float[]>();
    private Map<VirtualDoc, Float> sumSquareOfEntriesCache = new HashMap<VirtualDoc, Float>();
    
    /**
     * The VSM is set up for a set of virtual documents.
     * 
     * Get the similarity of two documents via method the 
     * following method:
     * cosineForVirtualDocs(VirtualDoc, VirtualDoc)
     * 
     * @param documents of the vector space
     */
	public VirtualDocVSM(Set<VirtualDoc> documents) {
        for (VirtualDoc d : documents) {
        	numberOfDocs++;
        	for (String term : d.getAllTerms()) {
        		if (!terms.contains(term))
        			terms.add(term);
        		if (!termToDocOccurrences.containsKey(term)) {
        			termToDocOccurrences.put(term, 1);
        		}
        		else {
        			int i = termToDocOccurrences.get(term);
        			termToDocOccurrences.put(term, ++i);
        		}
        	}
        	
        }
	}
	
	private float getTermWeightTFIDF(VirtualDoc doc, String term) {
		float tf = new Double(doc.getCountOfTerm(term) / doc.getMaxTermOccurrence()).floatValue();
		float idf = new Double(0.5 * (1 + Math.log(numberOfDocs / termToDocOccurrences.get(term)) / Math.log(2))).floatValue();
		
		return (tf * idf);
	}
	
	private float[] getVectorForDoc(VirtualDoc doc) {
		if (this.vectorCache.containsKey(doc))
			return this.vectorCache.get(doc);
			
		float[] vector = new float[this.terms.size()];
		for (int i = 0; i < this.terms.size(); i++) {
			if (doc.getCountOfTerm(this.terms.get(i)) != 0) {
				float termWeightInDoc = doc.getTermWeightInDoc(this.terms.get(i));
				float termWeightTFIDF = getTermWeightTFIDF(doc,this.terms.get(i));
				vector[i] = termWeightInDoc + termWeightTFIDF;
			}
			else {
				vector[i] = 0;
			}
		}
		this.vectorCache.put(doc, vector);
		
		return vector;
	}
	
	/**
	 * Sums up the squares of the vector entries.
	 * 
	 * @param vector
	 * @return sum of the squares of the vector.
	 */
	private float sumSquareOfEntries(float[] vector) {
		float sum = 0;
		for(int i = 0; i < vector.length; i++) {
			sum += vector[i] * vector[i];
		}
		return sum;
	}
	
	/**
	 * Wrapper for the calculation of the sum of the squares of vector entries.
	 * 
	 * @param doc
	 * @return sum of square of vector entries of vector representing doc
	 */
	private float sumSquareOfEntries(VirtualDoc doc) {
		if (!this.sumSquareOfEntriesCache.containsKey(doc))
			this.sumSquareOfEntriesCache.put(doc,sumSquareOfEntries(getVectorForDoc(doc)));
		
		return this.sumSquareOfEntriesCache.get(doc);
	}

	/**
	 * Returns the cosine of the vectors representing the documents in the VSM.
	 * 
	 * @param doc1
	 * @param doc2
	 * @return cosine between the vectors.
	 */
	public double cosineForVirtualDocs(VirtualDoc doc1, VirtualDoc doc2) {
		float[] vector1 = getVectorForDoc(doc1);
		float[] vector2 = getVectorForDoc(doc2);

		float product = 0;
		for(int i = 0; i < vector1.length; i++) {
			product += vector1[i] * vector2[i];
		}
		return product / Math.sqrt(sumSquareOfEntries(doc1) * sumSquareOfEntries(doc2));
	}
	
}
